//
//  BaseModel.swift
//
//  Created by Ink on 2024/6/21.
//  Copyright © 2024 ZM. All rights reserved.
//

import UIKit
import RxSwift
import BetterCodable

public protocol CodableModel : Codable {
    
    init(data: Data) throws
    init(_ json: String, using encoding: String.Encoding) throws
    func jsonData() throws -> Data
    func jsonString(encoding: String.Encoding) throws -> String?
    
    static func fromDictionary(_ dict: [String: Any]) -> Self?
    func toJsonString() -> String?
}


public extension CodableModel {
    
    init(data: Data) throws {
        self = try data.decoded() as Self
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    func jsonData() throws -> Data {
        return try self.encoded()
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

public extension CodableModel {
    static func fromDictionary(_ dict: [String: Any]) -> Self? {
        guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
              let model = try? JSONDecoder().decode(Self.self, from: data) else {
            return nil
        }
        return model
    }
    
    func toJsonString() -> String? {
        return toJson()?.toJsonString()
    }
}

public extension Encodable {
    func toDictionary(flatten: Bool = false, prefix: String = "") -> [String: Any]? {
        guard
            let data = try? JSONEncoder().encode(self),
            let jsonObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
        else { return nil }
        
        if flatten {
            return flattenDictionary(jsonObject, prefix: prefix)
        } else {
            return jsonObject
        }
    }
    
    private func flattenDictionary(_ dict: [String: Any], prefix: String) -> [String: Any] {
        var result: [String: Any] = [:]
        for (key, value) in dict {
            let newKey = prefix.isEmpty ? key : "\(prefix).\(key)"
            if let subDict = value as? [String: Any] {
                result.merge(flattenDictionary(subDict, prefix: newKey)) { $1 }
            } else {
                result[newKey] = value
            }
        }
        return result
    }
    func toJson() -> [String: Any]? { toDictionary() }
}

// MARK: - Encode/decode helpers

public extension Data {
    
    func toModel<D: Decodable>() -> D? {
        return try? decoded() as D
    }
    
    func toJson<T>() -> T? {
        do {
            return try JSONSerialization.jsonObject(with: self, options: .allowFragments) as? T
        } catch {
            print(error)
            return nil
        }
    }
    
    static func data(filePath:String?) -> Self? {
        guard let path = filePath else {
            return nil
        }
        do {
            let url = URL(fileURLWithPath: path)
            return try Data(contentsOf:url)
        } catch {
            print(error)
            return nil
        }
    }
}

public func ObjectToDictionary(_ object: Any) -> [String: Any] {
    var dict = [String: Any]()
    let mirror = Mirror(reflecting: object)
    
    // 遍历 Mirror 的 children
    for child in mirror.children {
        guard let propertyName = child.label else { continue }
        
        // 处理懒加载的对象，可能需要强制解包
        let value = child.value
        
        if let valueType = value as? CustomStringConvertible {
            dict[propertyName] = valueType.description
        } else {
            dict[propertyName] = ObjectToDictionary(value)
        }
    }
    
    return dict
}

public extension String {
    func toJsonData() -> Data? {
        return self.data(using: .utf8)
    }
    func toModel<D: Decodable>() -> D? {
        return toJsonData()?.toModel()
    }
}

public protocol ZMSavelocal {
        
    static var localPath: String { get }
    
    static func localData() -> Self?
    static func saveToLocal(data: Self?)
    static func deleateLocal()
    
    func saveToLocal()
}

extension ZMSavelocal {
    
    public static func deleateLocal() {
        FileManager.removeItem(path: self.localPath)
    }
    
    public static func saveToLocal(data: Self?) {
        if data == nil {
            FileManager.removeItem(path: localPath)
        }
        else {
            data?.saveToLocal()
        }
    }
}

extension ZMSavelocal where Self : Codable {
    
    public static func localData() -> Self? {
        let url = URL(fileURLWithPath: Self.localPath)
        if let data = try? Data(contentsOf:url) {
            return data.toModel()
        }
        return nil
    }
    
    public func saveToLocal() {
        do {
            let data = try encoded()
            let url = URL(fileURLWithPath: Self.localPath)
            try data.write(to: url)
        } catch {
            print(error)
        }
    }
}

extension ZMSavelocal where Self : ClassNameProtocol {
    public static var localPath: String {
        return FileManager.documentPath.append(path: self.className)
    }
}
